package cn.zebra.dev.config;

import cn.zebra.dev.common.utils.StrUtils;
import cn.zebra.dev.jwt.filter.JwtAuthenticationTokenFilter;
import cn.zebra.dev.security.entry.UnauthorizedAccessDeniedEntryPoint;
import cn.zebra.dev.security.handler.UnauthorizedAccessDeniedHandlerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.List;

/**
 * @author runnable@sina.cn
 */
@Configuration
@ConditionalOnProperty(value = SecurityConfigureBean.PREFIX + ".enable", matchIfMissing = true)
@ConfigurationProperties(prefix = SecurityConfigureBean.PREFIX)
public class SecurityConfigureBean extends WebSecurityConfigurerAdapter {

    static final String PREFIX = "zebra.security";
    /**
     * 退出用户接口地址
     */
    private String logout;

    private Boolean enable;

    @Autowired
    private IgnoreAuthConfigureBean configureBean;

    /**
     * 是否载入jwt过滤器
     */
    private Boolean jwt = false;

    @Autowired
    private JwtAuthenticationTokenFilter tokenFilter;

    @Autowired(required = false)
    private AccessDecisionManager accessDecisionManager;

    @Autowired(required = false)
    private FilterInvocationSecurityMetadataSource source;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //清除旧cookies
        http.logout().deleteCookies("JSESSIONID").invalidateHttpSession(true);
        http.csrf().disable();
        List<String> antGet = configureBean.getAntGet();
        List<String> antPost = configureBean.getAntPost();
        if (configureBean.getDruidEnable()) {
            antGet.add("druid/**");
            antPost.add("druid/**.json");
        }
        if (configureBean.getSwaggerEnable()) {
            antGet.add("doc.html");
            antGet.add("webjars/**");
            antGet.add("swagger-resources");
            antGet.add("swagger-resources/configuration/ui");
            antGet.add("v2/api-docs");
        }
        if (configureBean.getShowdownEnable()) {
            antPost.add("showdown");
        }
        setAnt(antGet);
        setAnt(antPost);
        configureBean.setAntGet(antGet);
        configureBean.setAntPost(antPost);
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
        registry
                // 允许对于网站静态资源的无授权访问
                .antMatchers(HttpMethod.GET, antGet != null ? antGet.toArray(new String[]{}) : new String[]{})
                .permitAll()
                .antMatchers(HttpMethod.POST, antPost != null ? antPost.toArray(new String[]{}) : new String[]{})
                .permitAll()
                //跨域请求会先进行一次options请求
                .antMatchers(HttpMethod.OPTIONS)
                .permitAll()
                .antMatchers("/**")
                // 除上面外的所有请求全部需要鉴权认证
                .authenticated()
                .and()
                .exceptionHandling().authenticationEntryPoint(new UnauthorizedAccessDeniedEntryPoint()).accessDeniedHandler(unauthorizedAccessDeniedHandler());
        if (accessDecisionManager != null || source != null) {
            registry.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O interceptor) {
                    //总是重新验证
                    interceptor.setAlwaysReauthenticate(false);
                    //发布授权成功
                    interceptor.setPublishAuthorizationSuccess(false);
                    //拒绝公共调用
                    interceptor.setRejectPublicInvocations(false);
                    //每次请求观察一次
                    interceptor.setObserveOncePerRequest(false);
                    //校验配置属性
                    interceptor.setValidateConfigAttributes(false);
                    if (accessDecisionManager != null) {
                        interceptor.setAccessDecisionManager(accessDecisionManager);
                    }
                    if (source != null) {
                        interceptor.setSecurityMetadataSource(source);
                    }
                    return interceptor;
                }
            });
        }
        if (StrUtils.isNotBlank(logout)) {
            http.logout().logoutUrl(logout);
        }
        if (jwt) {
            http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }

    @Bean
    public AccessDeniedHandler unauthorizedAccessDeniedHandler() {
        return new UnauthorizedAccessDeniedHandlerImpl();
    }

    private void setAnt(List<String> antMethods) {
        if (antMethods != null) {
            for (int i = antMethods.size() - 1; i >= 0; i--) {
                String get = antMethods.get(i);
                antMethods.set(i, "/" + get);
            }
        }
    }

    public Boolean getEnable() {
        return enable;
    }

    public void setEnable(Boolean enable) {
        this.enable = enable;
    }

    public Boolean getJwt() {
        return jwt;
    }

    public void setJwt(Boolean jwt) {
        this.jwt = jwt;
    }

    public String getLogout() {
        return logout;
    }

    public void setLogout(String logout) {
        this.logout = logout;
    }

}
